﻿#light

module Comparer

open System.Reflection
open Might.Infrastructure.ModelicaDOM
open System
open System.Collections.Generic

let isAnnotMappedProperty(prop:PropertyInfo) =
    let mapAtts = prop.GetCustomAttributes(typeof<MappingAttribute>,false)
    match mapAtts.Length with
    | 0 ->
        let defMapAtts = prop.GetCustomAttributes(typeof<DefaultMappingAttribute>,false)
        match defMapAtts.Length with
        | 0 ->
            let arrMapAtts = prop.GetCustomAttributes(typeof<ArrayIndexMappingAttribute>,false)
            match arrMapAtts.Length with
            | 0 ->
                let constMapAtts = prop.GetCustomAttributes(typeof<ConstructorIndexMappingAttribute>,false)
                match constMapAtts.Length with
                | 0 ->
                    false
                | _ ->
                    true
            | _ ->
                true
        | _ ->
            true
    | _ -> 
        true

let rec CompareLists(argLeft:System.Object,argRight:System.Object) =
    let argLeftType = argLeft.GetType()
    let argRightType = argRight.GetType()
    let bFlags = BindingFlags.Public ||| BindingFlags.InvokeMethod ||| BindingFlags.Instance
    let leftLength = argLeftType.InvokeMember("Count",bFlags,null,argLeft,[||]) :?> int
    let rightLength = argRightType.InvokeMember("Count",bFlags,null,argRight,[||]) :?> int
    let mutable eq = true    
    if( leftLength = rightLength ) then
        let leftEnumerator = argLeftType.InvokeMember("GetEnumerator",bFlags,null,argLeft,[||]) :?> IEnumerator<_>
        let rightEnumerator = argRightType.InvokeMember("GetEnumerator",bFlags,null,argRight,[||]) :?> IEnumerator<_>
        
        while ( leftEnumerator.MoveNext() = true && rightEnumerator.MoveNext() = true  && (eq <> false)) do
            let leftElement = leftEnumerator.Current
            let rightElement = rightEnumerator.Current
            let diffList: string list = Compare(leftElement,rightElement)
            if diffList.IsEmpty = false then
                eq <- false
    eq

and CompareProperties(propName:string,argLeft:System.Object,argRight:System.Object) =
    let argLeftType = argLeft.GetType()
    let argRightType = argRight.GetType()
    if argLeftType.Equals(argRightType) = false then
        raise(ArgumentException("Comparing non compatible types"))            
    
    let propLeft = argLeftType.GetProperty(propName)
    let propRight = argRightType.GetProperty(propName)
    let propLeftType = propLeft.PropertyType
    let propRightType = propRight.PropertyType
    if propLeftType.Equals(propRightType) = false then
        raise(ArgumentException("Comparing non compatible types"))
    
    let mutable eq = false
    let leftVal = propLeft.GetValue(argLeft,[||])
    let rightVal = propRight.GetValue(argRight,[||])
    
    if(propLeftType.IsPrimitive or propLeftType.Equals(typeof<string>) or propLeftType.IsEnum) then        
        eq <- leftVal.Equals(rightVal)
    if (propLeftType.GetInterface("IList",true) = null) then
        let diff: string list = Compare(leftVal,rightVal)
        if (diff.IsEmpty = true) then
            eq <- true       
    else
        eq <- CompareLists(leftVal,rightVal)        
    eq

and Compare(argLeft:System.Object,argRight:System.Object) = 
    let argLeftType = argLeft.GetType()
    let argRightType = argRight.GetType()    
    if argLeftType.Equals(argRightType) = false then
        raise(ArgumentException("Comparing non compatible types"))           
    let mutable diffList = []
    for prop in argLeftType.GetProperties() do
        if(isAnnotMappedProperty(prop) = true) then
            if( CompareProperties(prop.Name,argLeft,argRight) = false ) then
                diffList <- prop.Name :: diffList
    diffList